/*
 * Copyright (C) by Fluid Operations.
 * All rights reserved.
 *
 * For more information go to http://www.fluidops.com
 */

package com.fluidops.util.help.javahelp;

import java.io.File;
import java.io.IOException;
import java.io.StringBufferInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import com.fluidops.util.FileHelper;
import com.fluidops.util.help.HelpContent;
import com.fluidops.util.help.IndexItem;
import com.fluidops.util.help.TOCItem;
import com.fluidops.util.help.Topic;

/**
 * @author stefan
 *
 */
public class JavaHelpContent extends HelpContent
{
	// these files should always be ignored
	protected static String[] noContentFiles = new String[]
	{
		"properties.xml",
		"JavaHelpSearch/"
	};
	protected File tocConfigFile;
	protected File topicMapFile;
	protected File indexConfigFile;
	protected String homeID;
	protected Map<String, String> topic2Loc;
	
	public JavaHelpContent()
	{}
	
	public JavaHelpContent(HelpContent in)
	{
		super(in);
	}
	
	protected Document parseXML(File xmlFile) throws ParserConfigurationException, SAXException, IOException
	{
		DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		
		builder.setEntityResolver(new EntityResolver()
		{
			@Override
			public InputSource resolveEntity(String publicId, String systemId)
					throws SAXException, IOException
			{
				// return an empty DTD
				// this is required, since the toc_dtd of the JavaHelp
				// TOC-XML seems to be invalid and/or the SAX-parser does not like it
				return new InputSource(new StringBufferInputStream(""));
			}
		});
		
		return builder.parse(xmlFile);
	}
	
	protected TOCItem[] parseTOCItems(Node parent, Map<String, String> topicLoc, XPath xPath) throws XPathExpressionException
	{
		NodeList tocItems = (NodeList)xPath.evaluate("tocitem", parent, XPathConstants.NODESET);
		if(tocItems != null && tocItems.getLength() > 0)
		{
			TOCItem[] items = new TOCItem[tocItems.getLength()];
			
			for(int i = 0; i < items.length; i++)
			{
				items[i] = new TOCItem();
				
				Node item = tocItems.item(i);
				Node text = item.getAttributes().getNamedItem("text");
				if(text != null)
					items[i].title = text.getNodeValue();
				Node target = item.getAttributes().getNamedItem("target");
				if(target != null)
				{
					if(topicLoc.containsKey(target.getNodeValue()))
						items[i].target = topicLoc.get(target.getNodeValue());
					else
						items[i].target = target.getNodeValue();
				}
				items[i].subtopics = parseTOCItems(item, topicLoc, xPath);
			}
			
			return items;
		}
		else
			return null;
	}
	
	protected IndexItem[] parseIndexItems(Node parent, Map<String, String> topicLoc, XPath xPath) throws XPathExpressionException
	{
		NodeList tocItems = (NodeList)xPath.evaluate("indexitem", parent, XPathConstants.NODESET);
		if(tocItems != null && tocItems.getLength() > 0)
		{
			IndexItem[] items = new IndexItem[tocItems.getLength()];
			
			for(int i = 0; i < items.length; i++)
			{
				items[i] = new IndexItem();
				
				Node item = tocItems.item(i);
				Node text = item.getAttributes().getNamedItem("text");
				if(text != null)
					items[i].title = text.getNodeValue();
				Node target = item.getAttributes().getNamedItem("target");
				if(target != null)
				{
					items[i].topics = new Topic[1];
					items[i].topics[0] = new Topic();
					if(topicLoc.containsKey(target.getNodeValue()))
						items[i].topics[0].target = topicLoc.get(target.getNodeValue());
					else
						items[i].topics[0].target = target.getNodeValue();
				}
				items[i].subtopics = parseIndexItems(item, topicLoc, xPath);
			}
			
			return items;
		}
		else
			return null;
	}
	
	protected TOCItem[] parseTOCFile(File tocFile, Map<String, String> topicLoc) throws XPathExpressionException, ParserConfigurationException, SAXException, IOException
	{
		// get the list of topic-to-URL map items
		Document tocDoc = parseXML(tocFile);
		XPath xPath = XPathFactory.newInstance().newXPath();
		Node toc = (Node)xPath.evaluate("/toc", tocDoc, XPathConstants.NODE);
		
		return parseTOCItems(toc, topicLoc, xPath);
	}
	
	protected IndexItem[] parseIndexFile(File indexFile, Map<String, String> topicLoc) throws XPathExpressionException, ParserConfigurationException, SAXException, IOException
	{
		// get the list of topic-to-URL map items
		Document tocDoc = parseXML(indexFile);
		XPath xPath = XPathFactory.newInstance().newXPath();
		Node toc = (Node)xPath.evaluate("/index", tocDoc, XPathConstants.NODE);
		
		return parseIndexItems(toc, topicLoc, xPath);
	}
	
	protected Map<String, String> parseMapFile(File mapFile) throws XPathExpressionException, ParserConfigurationException, SAXException, IOException
	{
		// get the list of topic-to-URL map items
		Document mapDoc = parseXML(mapFile);
		XPath xPath = XPathFactory.newInstance().newXPath();
		NodeList mapIDs = (NodeList)xPath.evaluate("/map/mapID", mapDoc, XPathConstants.NODESET);
		
		Map<String, String> topics = new HashMap<String, String>();
		
		if(mapIDs != null)
		{
			for(int i = 0; i < mapIDs.getLength(); i++)
			{
				Node id = mapIDs.item(i);
				topics.put(id.getAttributes().getNamedItem("target").getNodeValue(), id.getAttributes().getNamedItem("url").getNodeValue());
			}
		}
		
		return topics;
	}
	
	protected String[] getFileList(File configFile, List<File> ignore)
	{
		String basePath = configFile.getParentFile().getAbsolutePath() + File.separator;
		
		List<File> filesUnclean = FileHelper.getFilesRecursive(configFile.getParentFile());
		
		// filter the unwanted files
		List<File> files = new ArrayList<File>();
		for(File f : filesUnclean)
		{
			boolean add = true;
			for(File i : ignore)
			{
				if(i.isDirectory())
				{
					if(f.getAbsolutePath().startsWith(i.getAbsolutePath()))
						add = false;
				}
				else if(i.equals(f))
					add = false;
			}
			if(add)
				files.add(f);
		}
		
		String[] res = new String[files.size()];
		int i = 0;
		for(File f : files)
		{
			String fp = f.getAbsolutePath();
			if(fp.startsWith(basePath))
				res[i++] = fp.substring(basePath.length());
			else
				res[i++] = fp;
		}
		
		return res;
	}
	
	public void parse(String confFile) throws Exception
	{
		List<File> ignore = new ArrayList<File>();
		
		File configFile = new File(confFile);
		basePath = configFile.getParent() + File.separator;
		
		ignore.add(configFile);
		for(String i : noContentFiles)
			ignore.add(new File(basePath + i));
		
		// get the root element of the JavaHelp config
		Document configDoc = parseXML(configFile);
		XPath xPath = XPathFactory.newInstance().newXPath();
		Node helpset = (Node)xPath.evaluate("/helpset", configDoc, XPathConstants.NODE);
		
		// what title does the help have?
		title = xPath.evaluate("title", helpset);
		
		// extract the TOC and index config files
		NodeList nodes = (NodeList)xPath.evaluate("view", helpset, XPathConstants.NODESET);
		if(nodes != null)
		{
			for(int i = 0; i < nodes.getLength(); i++)
			{
				Node node = nodes.item(i);
				String name = xPath.evaluate("name", node).toLowerCase().trim();
				if(name.equals("toc"))
				{
					tocConfigFile = new File(basePath + xPath.evaluate("data", node));
					ignore.add(tocConfigFile);
				}
				else if(name.equals("index"))
				{
					indexConfigFile = new File(basePath + xPath.evaluate("data", node));
					ignore.add(indexConfigFile);
				}
			}
		}
		
		// get the list of topic maps
		topic2Loc = new HashMap<String, String>();
		NodeList topicMaps = (NodeList)xPath.evaluate("maps/mapref", helpset, XPathConstants.NODESET);
		if(topicMaps != null)
		{
			for(int i = 0; i < topicMaps.getLength(); i++)
			{
				Node topicMap = topicMaps.item(i);
				String mapFile = topicMap.getAttributes().getNamedItem("location").getNodeValue();
				File map = new File(basePath + mapFile);
				ignore.add(map);
				
				topic2Loc.putAll(parseMapFile(map));
			}
		}
		
		// which is the home topic to be used?
		homeID = xPath.evaluate("maps/homeID", helpset);
		
		// parse in the toc
		toc = parseTOCFile(tocConfigFile, topic2Loc);
		
		// parse in the index
		index = parseIndexFile(indexConfigFile, topic2Loc);
		
		// get the list of files that belong to this help
		content = getFileList(configFile, ignore);
	}
	
	public void write(String configFile) throws Exception
	{
		throw new NotImplementedException();
	}
}
